package com.lzp.java.concurrent.threadcore.background;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 1. 测试a++线程不安全
 * 2. 打印并发出错的位置与次数
 * 3. 由于run方法中并发的部分比较多，index++占用时间很少，因此并发出错很少
 *
 * @author lzp
 * @date 2020/02/16
 */
public class MultiThreadError implements Runnable {
    static Runnable resource = new MultiThreadError();

    private static int index;
    /**
     * 真正加的数量
     */
    private static AtomicInteger realIndex = new AtomicInteger();
    /**
     * 出错的数量
     */
    private static AtomicInteger wrongIndex = new AtomicInteger();

    /**
     * 栅栏
     */
    static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    private boolean[] marked = new boolean[1000000];

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(resource);
        Thread thread2 = new Thread(resource);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("并发下的运行结果" + index);
        System.out.println("实际运行次数" + realIndex);
        System.out.println("错误运行次数" + wrongIndex);
    }

    @Override
    public void run() {
        // 0位置不会被设置，但是有判断
        marked[0] = true;
        for (int i = 0; i < 10000; i++) {
            try {
                // 重置
                cyclicBarrier2.reset();
                // 改进点1：加栅栏1--用于解决run1中的问题
                cyclicBarrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            index++;
            try {
                // 重置
                cyclicBarrier1.reset();
                // 改进点2：加栅栏--用于解决线程1执行较快到达条件判断时，线程2将index值改为2，导致错误的问题
                cyclicBarrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            realIndex.incrementAndGet();
            // 冲突时，index++均为1，可能线程1还未将marked[index]置为true，线程2已经判断结束，因此加synchronized
            synchronized (resource) {
                // 改进点3：加markded[index-1] -- 实际上是针对没有并发的处理
                // 原因：当线程1和线程2没有发生冲突时，线程执行完index++后，index=1，线程2执行完index++后，index=2
                // 因为栅栏的存在，等待线程1和线程2一起出发，此时index为2。synchronized具有可见性，
                // 假设线程1先取得锁，会将marked[2]置为true。线程2取得锁时，再次做marked[2]判断时便会报错
                if (marked[index] && marked[index - 1]) {
                    System.out.println("发生错误位置" + index);
                    wrongIndex.incrementAndGet();
                }
                marked[index] = true;
            }
        }
    }

    /**
     * 代码2的问题：
     * 假设index初识为0，线程1和2冲突，计算后index均为1
     * 我们会认为线程1执行完下面的代码后，线程2拿到锁，判断marked[index]为true，报错
     * 实际上很可能出现，线程1运行完代码进入下个迭代，将index值篡改为2，此时线程2进行marked[index]判断时仍然不会报错
     */
    public void run1() {
        for (int i = 0; i < 10000; i++) {
            index++;
            realIndex.incrementAndGet();
            synchronized (resource) {
                if (marked[index]) {
                    System.out.println("发生错误位置" + index);
                    wrongIndex.incrementAndGet();
                }
                marked[index] = true;
            }
        }
    }

    /**
     * 代码1的问题
     * 当线程1已经将marked[index]设置为1，线程2对相同的index做判断时会跳过报错语句
     */
    public void run0() {
        for (int i = 0; i < 10000; i++) {
            index++;
            realIndex.incrementAndGet();
            if (marked[index]) {
                System.out.println("发生错误位置" + index);
                wrongIndex.incrementAndGet();
            }
            marked[index] = true;
        }
    }

}
